package com.jfireframework.codejson.function;

import java.io.File;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.jfireframework.baseutil.collection.StringCache;
import com.jfireframework.baseutil.reflect.ReflectUtil;
import com.jfireframework.baseutil.smc.SmcHelper;
import com.jfireframework.baseutil.smc.compiler.JavaStringCompiler;
import com.jfireframework.baseutil.smc.model.CompilerModel;
import com.jfireframework.baseutil.smc.model.FieldModel;
import com.jfireframework.baseutil.smc.model.MethodModel;
import com.jfireframework.codejson.annotation.JsonIgnore;
import com.jfireframework.codejson.function.impl.write.IteratorWriter;
import com.jfireframework.codejson.function.impl.write.MapWriter;
import com.jfireframework.codejson.function.impl.write.StrategyMapWriter;
import com.jfireframework.codejson.function.impl.write.WriterAdapter;
import com.jfireframework.codejson.function.impl.write.array.BooleanArrayWriter;
import com.jfireframework.codejson.function.impl.write.array.ByteArrayWriter;
import com.jfireframework.codejson.function.impl.write.array.CharArrayWriter;
import com.jfireframework.codejson.function.impl.write.array.DoubleArrayWriter;
import com.jfireframework.codejson.function.impl.write.array.FloatArrayWriter;
import com.jfireframework.codejson.function.impl.write.array.IntArrayWriter;
import com.jfireframework.codejson.function.impl.write.array.LongArrayWriter;
import com.jfireframework.codejson.function.impl.write.array.ShortArrayWriter;
import com.jfireframework.codejson.function.impl.write.array.StringArrayWriter;
import com.jfireframework.codejson.function.impl.write.extra.ArrayListWriter;
import com.jfireframework.codejson.function.impl.write.extra.DateWriter;
import com.jfireframework.codejson.function.impl.write.extra.FileWriter;
import com.jfireframework.codejson.function.impl.write.wrapper.BooleanWriter;
import com.jfireframework.codejson.function.impl.write.wrapper.ByteWriter;
import com.jfireframework.codejson.function.impl.write.wrapper.CharacterWriter;
import com.jfireframework.codejson.function.impl.write.wrapper.DoubleWriter;
import com.jfireframework.codejson.function.impl.write.wrapper.FloatWriter;
import com.jfireframework.codejson.function.impl.write.wrapper.IntegerWriter;
import com.jfireframework.codejson.function.impl.write.wrapper.LongWriter;
import com.jfireframework.codejson.function.impl.write.wrapper.ShortWriter;
import com.jfireframework.codejson.function.impl.write.wrapper.StringWriter;
import com.jfireframework.codejson.methodinfo.MethodInfoBuilder;
import com.jfireframework.codejson.methodinfo.WriteMethodInfo;
import com.jfireframework.codejson.tracker.Tracker;
import com.jfireframework.codejson.util.MethodComparator;
import com.jfireframework.codejson.util.NameTool;

public class WriterContext
{
    private static ConcurrentHashMap<Class<?>, JsonWriter> writerMap = new ConcurrentHashMap<Class<?>, JsonWriter>();
    private static final Logger                            logger    = LoggerFactory.getLogger(WriterContext.class);
    private static AtomicInteger                           count     = new AtomicInteger(1);
    
    static
    {
        writerMap.put(String.class, new StringWriter());
        writerMap.put(Double.class, new DoubleWriter());
        writerMap.put(Float.class, new FloatWriter());
        writerMap.put(Integer.class, new IntegerWriter());
        writerMap.put(Long.class, new LongWriter());
        writerMap.put(Short.class, new ShortWriter());
        writerMap.put(Boolean.class, new BooleanWriter());
        writerMap.put(Byte.class, new ByteWriter());
        writerMap.put(Character.class, new CharacterWriter());
        writerMap.put(int[].class, new IntArrayWriter());
        writerMap.put(boolean[].class, new BooleanArrayWriter());
        writerMap.put(long[].class, new LongArrayWriter());
        writerMap.put(short[].class, new ShortArrayWriter());
        writerMap.put(byte[].class, new ByteArrayWriter());
        writerMap.put(float[].class, new FloatArrayWriter());
        writerMap.put(double[].class, new DoubleArrayWriter());
        writerMap.put(char[].class, new CharArrayWriter());
        writerMap.put(String[].class, new StringArrayWriter());
        writerMap.put(ArrayList.class, new ArrayListWriter());
        writerMap.put(Date.class, new DateWriter());
        writerMap.put(File.class, new FileWriter());
        writerMap.put(java.sql.Date.class, new DateWriter());
    }
    
    public static void write(Object entity, StringCache cache)
    {
        if (entity == null)
        {
            cache.append("null");
        }
        else
        {
            JsonWriter writer = getWriter(entity.getClass());
            try
            {
                writer.write(entity, cache, null, null);
            }
            catch (Throwable e)
            {
                e.printStackTrace();
            }
        }
    }
    
    public static JsonWriter getWriter(Class<?> ckass)
    {
        JsonWriter writer = writerMap.get(ckass);
        if (writer == null)
        {
            try
            {
                writer = (JsonWriter) createWriter(ckass, null).newInstance();
            }
            catch (Exception e)
            {
                throw new RuntimeException(e);
            }
            writerMap.putIfAbsent(ckass, writer);
        }
        return writer;
    }
    
    protected static JsonWriter getWriter(Class<?> ckass, WriteStrategy strategy)
    {
        // 这个方法每次都必须重新生成新的输出类。实际上这个方法并不是给用户直接调用的。而是给策略模式进行writer生成的。
        // 如果在这里使用writerMap进行判断，就会导致一种情况：前面已经进行过该类的输出生成了，现在有个新的策略，策略就无法生效。因为不会生成新的输出类
        Class<?> result = createWriter(ckass, strategy);
        try
        {
            Constructor<?> constructor = result.getConstructor(WriteStrategy.class);
            return (JsonWriter) constructor.newInstance(strategy);
        }
        catch (Exception e)
        {
            throw new RuntimeException(e);
        }
    }
    
    /**
     * 创建一个输出类cklas的jsonwriter
     * 
     * @param ckass
     * @return
     */
    private static Class<?> createWriter(Class<?> ckass, WriteStrategy strategy)
    {
        if (ckass.isArray())
        {
            return implWriter(implArrayWriterMethod(ckass, strategy), ckass, strategy);
        }
        else if (Iterable.class.isAssignableFrom(ckass))
        {
            return IteratorWriter.class;
        }
        else if (Map.class.isAssignableFrom(ckass))
        {
            if (strategy == null)
            {
                return MapWriter.class;
            }
            else
            {
                return StrategyMapWriter.class;
            }
        }
        else
        {
            return implWriter(implWriteMethod(ckass, strategy), ckass, strategy);
        }
    }
    
    private static String implWriteMethod(Class<?> ckass, WriteStrategy strategy)
    {
        StringCache cache = new StringCache();
        cache.append("{\r\nStringCache cache = (StringCache)$1;\r\n");
        cache.append("Tracker _$tracker = (Tracker)$3;\r\n");
        String entityName = "entity" + count.incrementAndGet();
        cache.append(SmcHelper.getTypeName(ckass) + " " + entityName + " =(" + SmcHelper.getTypeName(ckass) + " )$0;\r\n");
        if (strategy != null && strategy.isUseTracker())
        {
            cache.append("int _$reIndex = _$tracker.indexOf($0);\r\n");
        }
        cache.append("cache.append('{');\r\n");
        Method[] methods = ReflectUtil.listGetMethod(ckass);
        Arrays.sort(methods, new MethodComparator());
        for (Method each : methods)
        {
            if (needIgnore(each, strategy))
            {
                continue;
            }
            WriteMethodInfo methodInfo = MethodInfoBuilder.buildWriteMethodInfo(each, strategy, entityName);
            cache.append(methodInfo.getOutput());
        }
        cache.append("if(cache.isCommaLast())\r\n{\r\n\tcache.deleteLast();\r\n}\r\n");
        cache.append("cache.append('}');\r\n}");
        return cache.toString();
    }
    
    private static Class<?> implWriter(String methodBody, Class<?> ckass, WriteStrategy strategy)
    {
        try
        {
            CompilerModel compilerModel = new CompilerModel("JsonWriter_" + count.getAndIncrement(), WriterAdapter.class);
            compilerModel.addImport(StringCache.class, WriterContext.class, Tracker.class, Iterator.class, Map.class, Set.class, Entry.class, JsonWriter.class);
            if (strategy != null)
            {
                createStrategyConstructor(compilerModel);
            }
            Method method = WriterAdapter.class.getMethod("write", Object.class, StringCache.class, Object.class, Tracker.class);
            MethodModel methodModel = new MethodModel(method);
            methodModel.setBody(methodBody);
            compilerModel.putMethod(method, methodModel);
            logger.trace("{}创建的源码是\r{}\r", ckass.getName(), compilerModel.toStringWithLineNo());
            JavaStringCompiler compiler = new JavaStringCompiler();
            return compiler.compile(compilerModel, ckass.getClassLoader());
        }
        catch (Exception e)
        {
            throw new RuntimeException(e);
        }
    }
    
    private static void createStrategyConstructor(CompilerModel model)
    {
        model.addField(new FieldModel("writeStrategy", WriteStrategy.class));
        model.addConstructor("this.writeStrategy = $0;", WriteStrategy.class);
    }
    
    private static boolean needIgnore(Method method, WriteStrategy strategy)
    {
        String fieldName = ReflectUtil.getFieldNameFromMethod(method);
        if (method.isAnnotationPresent(JsonIgnore.class) && method.getAnnotation(JsonIgnore.class).force())
        {
            return true;
        }
        try
        {
            Field field = method.getDeclaringClass().getDeclaredField(fieldName);
            if (field.isAnnotationPresent(JsonIgnore.class) && field.getAnnotation(JsonIgnore.class).force())
            {
                return true;
            }
        }
        catch (Exception e)
        {
        }
        if (strategy != null)
        {
            if (strategy.ignore(method.getDeclaringClass().getName() + '.' + fieldName))
            {
                return true;
            }
            else
            {
                return false;
            }
        }
        if (method.isAnnotationPresent(JsonIgnore.class))
        {
            return true;
        }
        try
        {
            Field field = method.getDeclaringClass().getDeclaredField(fieldName);
            if (field.isAnnotationPresent(JsonIgnore.class))
            {
                return true;
            }
            else
            {
                return false;
            }
        }
        catch (Exception e)
        {
            return false;
        }
    }
    
    private static String implArrayWriterMethod(Class<?> targetClass, WriteStrategy strategy)
    {
        Class<?> rootType = targetClass;
        int dim = 0;
        while (rootType.isArray())
        {
            dim++;
            rootType = rootType.getComponentType();
        }
        String rootName = rootType.getName();
        String str;
        if (strategy == null)
        {
            str = "{\r\n\t" + NameTool.buildDimTypeName(rootName, dim) + " array" + dim + " = (" + NameTool.buildDimTypeName(rootName, dim) + ")$0;\r\n";
            str += "\tStringCache cache = (StringCache)$1;\r\n";
            str += "\tcache.append('[');\r\n";
            str += "\tint l" + dim + " = array" + dim + ".length;\r\n";
            String bk = "\t";
            for (int i = dim; i > 1; i--)
            {
                int next = i - 1;
                str += bk + "for(int i" + i + " = 0;i" + i + "<l" + i + ";i" + i + "++)\r\n";
                str += bk + "{\r\n";
                str += bk + "\tif(array" + i + "[i" + i + "] == null){continue;}\r\n";
                str += bk + "\tcache.append('[');\r\n";
                str += bk + "\t" + NameTool.buildDimTypeName(rootName, next) + " array" + next + " = array" + i + "[i" + i + "];\r\n";
                str += bk + "\tint l" + next + " =  array" + next + ".length;\r\n";
                bk += "\t";
            }
            str += bk + "for(int i1=0;i1<l1;i1++)\r\n";
            str += bk + "{\r\n";
            str += bk + "\tif(array1[i1]==null){continue;}\r\n";
            str += bk + "\tWriterContext.write(array1[i1],cache);\r\n";
            str += bk + "\tcache.append(',');\r\n";
            str += bk + "}\r\n";
            for (int i = dim; i > 1; i--)
            {
                str += bk + "if(cache.isCommaLast()){cache.deleteLast();}\r\n";
                str += bk + "cache.append(']');\r\n";
                str += bk + "cache.append(',');\r\n";
                bk = bk.substring(0, bk.length() - 1);
                str += bk + "}\r\n";
            }
            str += bk + "if(cache.isCommaLast()){cache.deleteLast();}\r\n";
            str += bk + "cache.append(']');\r\n";
            str += "}";
        }
        else
        {
            if (strategy.isUseTracker())
            {
                str = "{\r\n\t" + NameTool.buildDimTypeName(rootName, dim) + " array" + dim + " = (" + NameTool.buildDimTypeName(rootName, dim) + ")$0;\r\n";
                str += "\tStringCache cache = (StringCache)$1;\r\n";
                str += "\tTracker _$tracker = (Tracker)$3;\r\n";
                str += "\tint _$reIndex = _$tracker.indexOf($0);\r\n";
                str += "\tcache.append('[');\r\n";
                str += "\tint l" + dim + " = array" + dim + ".length;\r\n";
                String bk = "\t";
                for (int i = dim; i > 1; i--)
                {
                    int next = i - 1;
                    int pre = i + 1;
                    str += bk + "for(int i" + i + " = 0;i" + i + "<l" + i + ";i" + i + "++)\r\n";
                    str += bk + "{\r\n";
                    String nowArrayName = "array" + i + "[i" + i + "]";
                    String nowIndex = "_$index" + nowArrayName;
                    String preArrayName = "array" + pre + "[i" + pre + "]";
                    String preIndex = "_$index" + preArrayName;
                    str += bk + "\tif(" + nowArrayName + " == null){continue;}\r\n";
                    if (i == dim)
                    {
                        str += bk + "\t_$tracker.reset(_$reIndex);\r\n";
                    }
                    else
                    {
                        str += bk + "\t_$tracker.reset(" + preIndex + ");\r\n";
                    }
                    str += bk + "\tint " + nowIndex + " = _$tracker.indexOf(" + nowArrayName + ");\r\n";
                    str += bk + "\tif(" + nowArrayName + "!= -1)\r\n";
                    str += bk + "\t{\r\n";
                    String writerName = "writer_array_" + i;
                    str += bk + "\t\tJsonWriter " + writerName + " = writeStrategy.getTrackerType(" + nowArrayName + ".getClass());\r\n";
                    str += bk + "\t\tif(" + writerName + " != null)\r\n";
                    str += bk + "\t\t{\r\n";
                    str += bk + "\t\t\t" + writerName + ".write(" + nowArrayName + ",cache,$1,_$tracker);\r\n";
                    str += bk + "\t\t}\r\n";
                    str += bk + "\t\telse\r\n";
                    str += bk + "\t\t{\r\n";
                    str += bk + "\t\t\tcache.append(\"{\\\"$ref\\\":\\\"\").append(_$tracker.getPath(" + nowIndex + ")).append('\"').append('}');\r\n";
                    str += bk + "\t\t}\r\n";
                    str += bk + "\t\tcontinue;\r\n";
                    str += bk + "\t}\r\n";
                    str += bk + "\tcache.append('[');\r\n";
                    str += bk + "_$tracker.put(" + nowArrayName + ",\"[\"+i+" + i + "']',true);\r\n";
                    str += bk + "\t" + NameTool.buildDimTypeName(rootName, next) + " array" + next + " = array" + i + "[i" + i + "];\r\n";
                    str += bk + "\tint l" + next + " =  array" + next + ".length;\r\n";
                    bk += "\t";
                }
                str += bk + "for(int i1=0;i1<l1;i1++)\r\n";
                str += bk + "{\r\n";
                str += bk + "\tif(array1[i1]==null){continue;}\r\n";
                if (dim == 1)
                {
                    str += bk + "\t_$tracker.reset(_$reIndex);\r\n";
                }
                else
                {
                    str += bk + "\t_$tracker.reset(_$indexarray2[i2]);\r\n";
                }
                str += bk + "\tint _$indexarray0 = _$tracker.indexOf(array1[i1]);\r\n";
                str += bk + "\tif(_$indexarray0 != -1)\r\n";
                str += bk + "\t{\r\n";
                str += bk + "\t\tJsonWriter writer_array_0 = writeStrategy.getTrackerType(array1[i1].getClass());\r\n";
                str += bk + "\t\tif(writer_array_0 != null)\r\n";
                str += bk + "\t\t{\r\n";
                str += bk + "\t\t\twriter_array_0.write(array1[i1],cache,$0,_$tracker);\r\n";
                str += bk + "\t\t}\r\n";
                str += bk + "\t\telse\r\n";
                str += bk + "\t\t{\r\n";
                str += bk + "\t\t\tcache.append(\"{\\\"$ref\\\":\\\"\").append(_$tracker.getPath(_$indexarray0)).append('\"').append('}');\r\n";
                str += bk + "\t\t}\r\n";
                str += bk + "\t\tcontinue;\r\n";
                str += bk + "\t}\r\n";
                str += bk + "\t_$tracker.put(array1[i1],\"[\"+i1+']',true);\r\n";
                str += bk + "\twriteStrategy.getWriter(array1[i1].getClass()).write(array1[i1],cache,$0,_$tracker);\r\n";
                str += bk + "\tcache.append(',');\r\n";
                str += bk + "}\r\n";
                for (int i = dim; i > 1; i--)
                {
                    str += bk + "if(cache.isCommaLast()){cache.deleteLast();}\r\n";
                    str += bk + "cache.append(']');\r\n";
                    str += bk + "cache.append(',');\r\n";
                    bk = bk.substring(0, bk.length() - 1);
                    str += bk + "}\r\n";
                }
                str += bk + "if(cache.isCommaLast()){cache.deleteLast();}\r\n";
                str += bk + "cache.append(']');\r\n";
                str += "}";
            }
            else
            {
                str = "{\r\n\t" + NameTool.buildDimTypeName(rootName, dim) + " array" + dim + " = (" + NameTool.buildDimTypeName(rootName, dim) + ")$0;\r\n";
                str += "\tStringCache cache = (StringCache)$1;\r\n";
                str += "\tcache.append('[');\r\n";
                str += "\tint l" + dim + " = array" + dim + ".length;\r\n";
                String bk = "\t";
                for (int i = dim; i > 1; i--)
                {
                    int next = i - 1;
                    str += bk + "for(int i" + i + " = 0;i" + i + "<l" + i + ";i" + i + "++)\r\n";
                    str += bk + "{\r\n";
                    str += bk + "\tif(array" + i + "[i" + i + "] == null){continue;}\r\n";
                    str += bk + "\tcache.append('[');\r\n";
                    str += bk + "\t" + NameTool.buildDimTypeName(rootName, next) + " array" + next + " = array" + i + "[i" + i + "];\r\n";
                    str += bk + "\tint l" + next + " =  array" + next + ".length;\r\n";
                    bk += "\t";
                }
                str += bk + "for(int i1=0;i1<l1;i1++)\r\n";
                str += bk + "{\r\n";
                str += bk + "\tif(array1[i1]==null){continue;}\r\n";
                str += bk + "\twriteStrategy.getWriter(array1[i1].getClass()).write(array1[i1],cache,$0,null);\r\n";
                str += bk + "\tcache.append(',');\r\n";
                str += bk + "}\r\n";
                for (int i = dim; i > 1; i--)
                {
                    str += bk + "if(cache.isCommaLast()){cache.deleteLast();}\r\n";
                    str += bk + "cache.append(']');\r\n";
                    str += bk + "cache.append(',');\r\n";
                    bk = bk.substring(0, bk.length() - 1);
                    str += bk + "}\r\n";
                }
                str += bk + "if(cache.isCommaLast()){cache.deleteLast();}\r\n";
                str += bk + "cache.append(']');\r\n";
                str += "}";
            }
        }
        return str;
    }
    
    public static void putwriter(Class<?> cklass, JsonWriter jsonWriter)
    {
        writerMap.put(cklass, jsonWriter);
    }
}
